﻿using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Xna.Framework;

namespace Ratatoskr.Toolbox
{
    public class Grid<T> : Entity where T : Cell
    {

        #region Fields, Properties & Indexer

        protected readonly ConstructorInfo _cellConstructor = typeof(T).GetConstructor(new Type[] { typeof(int), typeof(int) });

        protected int _xMin;
        protected int _yMin;
        protected int _xMax;
        protected int _yMax;
        /// <summary>
        /// Inclusive length of the X and Y axes.
        /// </summary>
        public Vector2 Dimensions
        {
            get { return new Vector2(_xMax - _xMin + 1, _yMax - _yMin + 1); }
        }
        /// <summary>
        /// Minimum X and Y coordinates.
        /// </summary>
        public Vector2 MinExtent
        {
            get { return new Vector2(_xMin, _yMin); }
        }
        /// <summary>
        /// Maximum X and Y coordinates.
        /// </summary>
        public Vector2 MaxExtent
        {
            get { return new Vector2(_xMax, _yMax); }
        }

        /// <summary>
        /// Returns the cell located at x,y on the grid. (Non-square grid types must map to integer cartesian coordinates.)
        /// </summary>
        /// <param name="x">X coordinate of cell.</param>
        /// <param name="y">Y coordinate of cell.</param>
        /// <returns>Cell at x,y.</returns>
        public T this[int x, int y]
        {
            get
            {
                if (x > _xMax || x < _xMin)
                    throw new ArgumentOutOfRangeException("X coordinate is outside of existing grid.");
                if (y > _yMax || y < _yMin)
                    throw new ArgumentOutOfRangeException("Y coordinate is outside of existing grid.");
                return (T)GetChild("cell_" + x + "_" + y);
            }
        }

        #endregion

        #region Constructor & Cell Construction

        public Grid(int xMin = 0, int yMin = 0, int xMax = 0, int yMax = 0, string name = null)
            : base(name)
        {
            if (xMin > 0)
                throw new ArgumentOutOfRangeException("Minimum X coordinate must be 0 or less.");
            if (yMin > 0)
                throw new ArgumentOutOfRangeException("Minimum Y coordinate must be 0 or less.");
            if (xMax < 0)
                throw new ArgumentOutOfRangeException("Maximum X coordinate must be 0 or more.");
            if (yMax < 0)
                throw new ArgumentOutOfRangeException("Maximum Y coordinate must be 0 or more.");
            _xMin = xMin;
            _yMin = yMin;
            _xMax = xMax;
            _yMax = yMax;
            GridOperation(_xMin, _xMax, _yMin, _yMax, (x, y) =>
                ConstructCell(x, y));
            GridOperation(_xMin, _xMax, _yMin, _yMax, (x, y) =>
                this[x, y].FindAdjacentCells());
        }

        public Grid(int xMax = 0, int yMax = 0, string name = null)
            : this(0, 0, xMax, yMax, name)
        {
        }

        public Grid(string name = null)
            : this(0, 0, 0, 0, name)
        {
        }
    
        protected virtual void ExtendConstructCell(T cell, int x, int y) { }

        protected void ConstructCell(int x, int y)
        {
            T c = (T)_cellConstructor.Invoke(new object[] { this, x, y });
            c.Attach(this);
            if (this[x, y] == c)
                ExtendConstructCell(c, x, y);
        }

        #endregion

        #region Grid Operations & Size Adjustment

        public delegate void CellOperation(int x, int y);

        public void GridOperation(int xMin, int xMax, int yMin, int yMax, CellOperation process)
        {
            for (int x = xMin; x <= xMax; x++)
                for (int y = yMin; y <= yMax; y++)
                    process(x, y);
        }

        public void ResizeBy(int dxMin, int dxMax, int dyMin, int dyMax)
        {
            try { ResizeTo(_xMin + dxMin, _xMax + dxMax, _yMin + dyMin, _yMax + dyMax); }
            catch (ArgumentException e) { throw e; }
        }

        public void ResizeTo(int xMin, int xMax, int yMin, int yMax, CellOperation fillOp = null)
        {
            if (xMin > 0)
                throw new ArgumentOutOfRangeException("Minimum X coordinate must be 0 or less.");
            if (yMin > 0)
                throw new ArgumentOutOfRangeException("Minimum Y coordinate must be 0 or less.");
            if (xMax < 0)
                throw new ArgumentOutOfRangeException("Maximum X coordinate must be 0 or more.");
            if (yMax < 0)
                throw new ArgumentOutOfRangeException("Maximum Y coordinate must be 0 or more.");
            if (fillOp == null)
                fillOp = ConstructCell;
            int border;
            CellOperation op;
            if (xMin != _xMin)
            {
                if (xMin < _xMin)
                {
                    border = _xMin;
                    _xMin = xMin;
                    op = fillOp;
                }
                else
                {
                    border = xMin;
                    op = (x, y) => this[x, y].Dispose();
                }
                GridOperation(_xMin, border - 1, _yMin, _yMax, op);
                GridOperation(border, border, _yMin, _yMax, (x, y) =>
                    this[x, y].FindAdjacentCells());
                _xMin = xMin;
            }
            if (xMax != _xMax)
            {
                if (xMax > _xMax)
                {
                    border = _xMax;
                    _xMax = xMax;
                    op = fillOp;
                }
                else
                {
                    border = xMax;
                    op = (x, y) => this[x, y].Dispose();
                }
                GridOperation(border + 1, _xMax, _yMin, _yMax, op);
                GridOperation(border, border, _yMin, _yMax, (x, y) =>
                    this[x, y].FindAdjacentCells());
                _xMax = xMax;
            }
            if (yMin != _yMin)
            {
                if (yMin < _yMin)
                {
                    border = _yMin;
                    _yMin = yMin;
                    op = fillOp;
                }
                else
                {
                    border = yMin;
                    op = (x, y) => this[x, y].Dispose();
                }
                GridOperation(_xMin, _xMax, _yMin, border - 1, op);
                GridOperation(_xMin, _xMax, border, border, (x, y) =>
                    this[x, y].FindAdjacentCells());
                _yMin = yMin;
            }
            if (yMax != _yMax)
            {
                if (yMax > _yMax)
                {
                    border = _yMax;
                    _yMax = yMax;
                    op = fillOp;
                }
                else
                {
                    border = yMax;
                    op = (x, y) => this[x, y].Dispose();
                }
                GridOperation(_xMin, _xMax, border + 1, _yMax, op);
                GridOperation(_xMin, _xMax, border, border, (x, y) =>
                    this[x, y].FindAdjacentCells());
                _yMax = yMax;
            }
        }

        #endregion

    }
}